﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Roslyn.Compiler.Extension.Attributes.Converter;
using Roslyn.Compiler.Extension.Values;
using Roslyn.Compilers.Common;
using Roslyn.Compilers.CSharp;
using Roslyn.Compilers;
using Roslyn.Services;


namespace Roslyn.Compiler.Extension.CustomSyntaxRewriter
{
    /// <summary>
    /// 1.6 Field Converted
    /// 1.6 Properties Converted
    /// </summary>
    public class MultiplicityRewriter : SyntaxRewriter
    {
        private readonly ISolution _solution;
        private readonly ISemanticModel _semanticModel;
        private static ReferencesStack _nodeReferenceLocations 
            = new ReferencesStack();

        private MultiplicityRewriter(ISolution solution, ISemanticModel semanticModel)
        {
            this._semanticModel = semanticModel;
            _solution = solution;
        }

        public static ISolution Transform(ISolution solution)
        {
            ReferencesStack nodeReferenceLocations = new ReferencesStack();
            ISolution newSolution = solution;
            //For now only use this project. Later we need to iterate over all projects
            IProject project = solution.Projects.Where(p => p.Name.Equals("Example.Linq")).First();
            //foreach (var project in solution.Projects)
            //{
            //}
            MultiplicityRewriter rewriter = null; ;
            foreach (var document in project.Documents)
            {
                string code = document.GetSyntaxRoot().ToFullString();
                ISemanticModel model = document.GetSemanticModel();
                rewriter = new MultiplicityRewriter(solution, model);
                SyntaxNode node = (SyntaxNode)document.GetSyntaxTree().GetRoot();
                SyntaxNode newSource = rewriter.Visit(node);
                newSolution = newSolution.UpdateDocument(document.Id, newSource);
               // nodeReferenceLocations.Push(
            }
            newSolution = rewriter.ProcessReferences(newSolution, _nodeReferenceLocations);
           
            return newSolution;
            
        }


        private static List<FieldDeclarationSyntax> fieldsToTransform = new List<FieldDeclarationSyntax>();
        private ISolution ProcessReferences(ISolution solution, ReferencesStack _nodeReferenceLocations)
        {
            ISolution updatedSolution = solution;
            foreach (FieldDeclarationSyntax node in fieldsToTransform)
            {
                foreach (VariableDeclaratorSyntax declarationSyntax in node.Declaration.Variables)
                {
                    //Find all references
                    ISymbol nodeSymbol = _semanticModel.GetDeclaredSymbol(node);
                    IEnumerable<ReferencedSymbol> referencedSymbols = nodeSymbol.FindReferences(_solution);
                    foreach (var referencedSymbol in referencedSymbols)
                    {
                        var syntaxTokens = from reference in nodeSymbol.FindReferences(updatedSolution)
                                           from loc in reference.Locations
                                           let position = loc.Location.SourceSpan.Start
                                           select loc.Location.SourceTree.GetRoot().FindToken(position) into token
                                           select token;
                    }
                }
           
            }
           // updatedSolution = OldMethod(solution, _nodeReferenceLocations, updatedSolution);
            return updatedSolution;
            //foreach (ReferenceLocation loc in findResults.First().Locations)
            //{
            //    int start = loc.Location.SourceSpan.Start;
            //    CommonSyntaxToken token = loc.Location.SourceTree.GetRoot().FindToken(start);
            //    CommonSyntaxNode syntaxNode = token.Parent;

            //}
        }

        private static ISolution OldMethod(ISolution solution, ReferencesStack _nodeReferenceLocations, ISolution updatedSolution)
        {
            while (_nodeReferenceLocations.Count > 0)
            {
                var nodeReferences = _nodeReferenceLocations.Pop();
                var syntaxTokens = from reference in nodeReferences.Key.FindReferences(updatedSolution)
                                   from loc in reference.Locations
                                   let position = loc.Location.SourceSpan.Start
                                   select loc.Location.SourceTree.GetRoot().FindToken(position) into token
                                   select token;

                //TODO:Need to order by line number from bottom to top. otherwise we won't find our nodes again
                ILookup<CommonSyntaxNode, CommonSyntaxToken> lookUp = syntaxTokens.ToLookup(t => t.SyntaxTree.GetRoot());

                foreach (var treeRoot in lookUp)
                {
                    IDocument doc = updatedSolution.GetDocument(treeRoot.Key.GetLocation().SourceTree);
                    CommonSyntaxNode myRoot = doc.GetSyntaxRoot();

                    var root = (CompilationUnitSyntax)treeRoot.Key;

                    foreach (var token in treeRoot)
                    {
                        var tok = myRoot.FindToken(token.GetLocation().SourceSpan.Start);
                        //TODO: Check if this is always correct
                        CommonSyntaxNode node = tok.Parent.Parent.Parent;

                        switch ((SyntaxKind)node.Kind)
                        {
                            case SyntaxKind.InvocationExpression:
                                //rule: _email.Contains("def"); => foreach(var email in _email) email.Contains("def") 
                                ExpressionStatementSyntax oldinvocationSyntax = (ExpressionStatementSyntax)node.Parent;
                                SyntaxTriviaList leadingWhitespaces = oldinvocationSyntax.GetLeadingTrivia();
                                oldinvocationSyntax = oldinvocationSyntax.WithLeadingTrivia(Syntax.Space);
                                //Build up the foreachloop
                                //TODO:Question does var always work?
                                TypeSyntax loopType = Syntax.ParseTypeName("var ");
                                //Build up the loop Variable
                                IdentifierNameSyntax identifier =
                                    (IdentifierNameSyntax)((MemberAccessExpressionSyntax)((InvocationExpressionSyntax)oldinvocationSyntax.Expression).Expression).Expression;
                                ExpressionSyntax exp = Syntax.ParseExpression(string.Format(" {0}", identifier.Identifier.Value.ToString()));

                                //TODO: PROPER NAME FOR THE LOOPVARIABLE
                                ForEachStatementSyntax foreachLoop = Syntax.ForEachStatement(loopType, "aloopvariable ", exp, oldinvocationSyntax);
                                foreachLoop = foreachLoop.WithLeadingTrivia(leadingWhitespaces);
                                //end foreachloop
                                myRoot = myRoot.ReplaceNode(node.Parent, foreachLoop);
                              //  myRoot.ReplaceNodes(new List<Synt
                                break;
                        }

                    }
                    IDocument originalDoc = solution.GetDocument(((CommonSyntaxNode)root).GetLocation().SourceTree);
                    updatedSolution = updatedSolution.UpdateDocument(originalDoc.Id, myRoot);
                    //dosomething with the token
                }



            }
            return updatedSolution;
        }

        #region overriden visitor methods
        public override SyntaxNode VisitFieldDeclaration(FieldDeclarationSyntax node)
        {
            List<AttributeSyntax> multiplicityAttributes = Utilities.GetMultiplicityAttributes(_semanticModel, node);
        
            if(multiplicityAttributes == null || multiplicityAttributes.Count == 0)
                return base.VisitFieldDeclaration(node);
 
            //Now there should be only one multiplicityAttribute. Proceed with changing to necessary type
            AttributeSyntax syntax = multiplicityAttributes.First();
            AttributeConverter converter = AttributeConverter.GetInstance(_solution,_semanticModel, syntax);
           
         //   FindAllReferences(node);
            FieldDeclarationSyntax newSyntax = converter.ConvertField(node);
            if (converter is AnyAttributeConverter)
                fieldsToTransform.Add(newSyntax);
           // SyntaxTriviaList list =  node.GetLeadingTrivia();
            return newSyntax;
        }

        public override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax node)
        {
            
            List<AttributeSyntax> multiplicityAttributes = Utilities.GetMultiplicityAttributes(_semanticModel, node);
            
            if (multiplicityAttributes == null || multiplicityAttributes.Count == 0)
                return base.VisitPropertyDeclaration(node);

            AttributeSyntax syntax = multiplicityAttributes.First();
            AttributeConverter converter = AttributeConverter.GetInstance(_solution,_semanticModel, syntax);
            PropertyDeclarationSyntax newSyntax = converter.ConvertProperty(node);
        
            return newSyntax;
        }

        public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node)
        {
            return base.VisitMethodDeclaration(node);
            List<AttributeSyntax> multiplicityAttributes = Utilities.GetMultiplicityAttributes(_semanticModel, node);

            if (multiplicityAttributes == null || multiplicityAttributes.Count == 0)
                return base.VisitMethodDeclaration(node);

            AttributeSyntax syntax = multiplicityAttributes.First();
            AttributeConverter converter = AttributeConverter.GetInstance(_solution,_semanticModel, syntax);
            MethodDeclarationSyntax newSyntax = converter.ConvertMethod(node);
            //TODO:Convert Method Attribute
            return newSyntax;
        }

        
        /// <summary>
        /// TODO: currently only working for FieldDeclerations
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        //public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node)
        //{
        //  //  FindDecleration(node);
        //    MemberAccessExpressionSyntax memberAccessExpression = ((MemberAccessExpressionSyntax)node.Expression);
        //    IdentifierNameSyntax identifier = (IdentifierNameSyntax)memberAccessExpression.Expression;
        //    FieldDeclarationSyntax declerationNode = FindDecleration(identifier);

        //    if (declerationNode == null) return base.VisitInvocationExpression(node);
            
        //    List<AttributeSyntax> multiplicityAttributes = Utilities.GetMultiplicityAttributes(_semanticModel, declerationNode);
            
        //    //
        //    if (multiplicityAttributes == null || multiplicityAttributes.Count == 0)
        //        return base.VisitInvocationExpression(node);

        //    ExpressionStatementSyntax oldinvocationSyntax = (ExpressionStatementSyntax)node.Parent;
        //    SyntaxTriviaList leadingWhitespaces = oldinvocationSyntax.GetLeadingTrivia();
        //    oldinvocationSyntax = oldinvocationSyntax.WithLeadingTrivia(Syntax.Space);
        //    //Build up the foreachloop
        //    //TODO:Question does var always work?
        //    TypeSyntax loopType = Syntax.ParseTypeName("var ");
        //    //Build up the loop Variable
        //    ExpressionSyntax exp = Syntax.ParseExpression(string.Format(" {0}", identifier.Identifier.Value.ToString()));

        //    //TODO: PROPER NAME FOR THE LOOPVARIABLE
            
        //    ForEachStatementSyntax foreachLoop = Syntax.ForEachStatement(loopType, "aloopvariable ", exp, oldinvocationSyntax);
            
        //    foreachLoop = foreachLoop.WithLeadingTrivia(leadingWhitespaces);
        //    StatementSyntax test = Syntax.ParseStatement(foreachLoop.ToFullString());

            
        //    //ExpressionSyntax expSyntax = Syntax.ParseExpression(foreachLoop.ToFullString());
        //    ////end foreachloop
        //    //node.
        //    //node.WithExpression(foreachLoop);
        //    return node;
        //}
        
        #endregion

        private FieldDeclarationSyntax FindDecleration(CommonSyntaxNode identifier)
        {
            //Roslyn.Compilers.CSharp.Source
           // _semanticModel.
            CommonSymbolInfo symbolInfo = _semanticModel.GetSymbolInfo(identifier);
            ISymbol symbol = symbolInfo.Symbol;
            ISymbol originalDefinition = symbol.OriginalDefinition;

            //Bad but recommended here http://stackoverflow.com/questions/11428662/how-to-find-the-syntaxnode-for-a-method-symbol-in-a-compilationunit
            var position = symbol.Locations.First().SourceSpan.Start;
            var node = symbol.Locations.First().SourceTree.GetRoot().FindToken(position).Parent.FirstAncestorOrSelf<FieldDeclarationSyntax>();

            return node;
        }

        private IEnumerable<ReferencedSymbol> FindAllReferences(VariableDeclaratorSyntax node)
        {


             ISymbol nodeSymbol = _semanticModel.GetDeclaredSymbol(node);
             IEnumerable<ReferencedSymbol> referencedSymbols = nodeSymbol.FindReferences(_solution);
             return referencedSymbols;
                
            
        }
    }
}
